package youfu

import (
	"bytes"
	"crypto/rsa"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"sync"
	"time"
)

const (
	//测试环境地址
	TestDoamin = "http://testgateway.serviceshare.com/testapi/clientapi/clientBusiness/common"
)

type Client struct {
	mutex sync.Mutex

	// 时序信息
	reqId string

	apiDoamin          string // api请求域名
	notifyVerifyDoamin string // 返回报文验证域名
	merId              string // 商户号

	// 信息加解密相关
	desKey         string          // DES密钥
	youfuRsaPubKey *rsa.PublicKey  // 优付rsa公钥
	merPrivateKey  *rsa.PrivateKey // 商户rsa私钥
	merPublicKey   *rsa.PublicKey  //商户rsa公钥

	// http处理相关
	Client          *http.Client
	location        *time.Location
	RequestUrl      string // 请求地址
	RequestContent  string // 请求消息Json编码
	ResponseContent string //响应消息Json编码
}

func NewClient(ownerRsaPriKey string, ownerRsaPubKey string, youfuRsaPubKey string, desKey string, merId string) (*Client, error) {
	client := &Client{}
	client.merId = merId

	// 加载商户私钥
	client.merPrivateKey = ParsePKCS8PrivateKey(ownerRsaPriKey)

	// 加载商户公钥
	client.merPublicKey = ParsePublicKey(ownerRsaPubKey)

	// 加载优付公钥
	client.youfuRsaPubKey = ParsePublicKey(youfuRsaPubKey)

	// 传递des key
	// des key 有个很神奇的事情，32位的密钥，只需要取前8位就能算出和32位相同加解密结果
	// 且golang的des库只支持8位密钥，或者三重des密钥(24位)
	if len(desKey) == 32 {
		client.desKey = desKey[0:8]
	} else if len(desKey) == 24 {
		client.desKey = desKey
	} else if len(desKey) == 8 {
		client.desKey = desKey
	} else {
		return nil, errors.New("unkowned desKey length")
	}

	// 创建一个用于请求的http客户端
	client.Client = &http.Client{Timeout: 180 * time.Second}
	location, err := time.LoadLocation("Local")
	if err != nil {
		return nil, errors.New("LoadLocation failed: " + err.Error())
	}
	client.location = location

	client.apiDoamin = TestDoamin

	return client, nil
}

func (cli *Client) Post(reqMsg *ReqMessage) ([]byte, error) {
	reqBody := bytes.NewBuffer([]byte(jsonMarshal(reqMsg)))

	fmt.Printf("post api : %s\n", cli.apiDoamin)
	fmt.Printf("【HTTP请求报文】: %s\n", jsonMarshal(reqMsg))
	httpReq, err := http.NewRequest("POST", cli.apiDoamin, reqBody)
	if err != nil {
		return nil, errors.New("NewRequest failed. err: " + err.Error())
	}

	httpReq.Close = true
	httpReq.Header.Set("Content-Encoding", "UTF8")
	httpReq.Header.Set("Content-Type", "application/json;charset=UTF8")
	resp, err := cli.Client.Do(httpReq)
	if err != nil {
		return nil, errors.New("Client Do failed. err: " + err.Error())
	}

	respData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, errors.New("ReadAll failed. err:" + err.Error())
	}

	if resp.StatusCode != 200 {
		errStr := fmt.Sprintf("Http request response StatusCode:%v detail:%s", resp.StatusCode, string(respData))
		return nil, errors.New(errStr)
	}
	fmt.Printf("【HTTP返回报文】: %s\n", jsonMarshal(respData))
	return respData, nil
}

func (cli *Client) buildCommReq(reqJson string, funCode FunCodeString) (*ReqMessage, error) {
	// 注：PHP不需要将reqData明文转化为字节数组，PHP的demo中已对密文进行Base64编码,
	// 如果用PHPdemo中的加解密方式注意不必再次进行Base64编码
	// 如下为Java流程示例，其他语言可能会略有不同
	// （注意git 中Java demo中的签名方法结束后对签名结果做了Base64编码，如不使用Java demo中签名方法或使用其他编程语言开发，
	// 请注意实现方式）。

	// （1）商户构造业务数据reqData明文，Json格式字符串形式。
	fmt.Printf("【构建请求报文】开始: %s\n", reqJson)
	// （2）商户将reqData明文转化为字节数组，编码格式为:UTF-8。Go的字符串默认是UTF-8编码，因此不用额外处理
	reqData := []byte(reqJson)
	// （3）商户得到reqData明文字节数组，用优付DES密钥加密得到密文字节数组。
	entryptReqData := DesECBEntrypt(reqData, []byte(cli.desKey))
	// （4）商户将密文字节数组用Base64编码得到Base64编码后的密文字节数组。
	entryReqBase64 := base64Encode(entryptReqData)
	//fmt.Printf("【密文base64字符串】 : %s\n", entryReqBase64)

	// （5）将Base64编码后的密文字节数组放入公共请求参数（详见4.3）reqData字段。
	commReqMessage := &ReqMessage{}
	commReqMessage.ReqData = entryReqBase64
	// （6）将Base64编码后的密文字节数组用商户RSA私钥进行签名，然后将签名结果放入公共请求参数（详见4.3）sign字段。
	sign, err := getRSASign(entryReqBase64, cli.merPrivateKey)
	if err != nil {
		return nil, errors.New("TransReqJsonToParamData, Sign with rsa private key failed. err: " + err.Error())
	}
	commReqMessage.Sign = base64Encode([]byte(sign))
	//fmt.Printf("【resData签名】 : %s\n", commReqMessage.Sign)

	// 填充剩余的公共信息
	commReqMessage.ReqId = cli.reqId
	commReqMessage.FunCode = funCode
	commReqMessage.Version = API_VERSION
	commReqMessage.MerId = cli.merId
	fmt.Printf("【构建请求报文】结束 : %s\n", jsonMarshal(commReqMessage))
	return commReqMessage, nil
}

//验签解析数据
func (cli *Client) parseCommResp(resp []byte) (*RespMessage, error) {
	// （1）商户收到优付返回数据 resData密文（优付DES加密）和 sign字段密文（优付对resData密文再用优付RSA私钥签名后的数据）。
	// （5）商户将明文字节数组转化为字符串进行后续业务逻辑处理。
	respMsg := &RespMessage{}
	err := json.Unmarshal(resp, &respMsg)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal. err: %s", err.Error())
	}
	cli.merPublicKey = ParsePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCXFtJNcCv9X+pfOz+jy4WO+7KC0tdtDGfW/XrJOFn6mTR/TpJrsLzwssTyj0hDfUla1xDCbAWlUEkzb64jsPKk9WACupLOOrnEy7NuG0O5jg36UXle+m5/uDm8B/KGV9BQmEt+EOIbl2IzNYS4GflkbCTtkbzMc+DXetl3IHM1+wIDAQAB")
	if len(respMsg.Sign) > 0 {
		// （2）商户使用优付RSA公钥进行验签（即验证解密sign字段密文和resData密文是否一致），验签成功，说明数据来自优付且没有被篡改，方可处理resData数据。
		verifyRsaSign := verifyRsaSign(respMsg.ResData, cli.youfuRsaPubKey, respMsg.Sign)

		if verifyRsaSign != nil {
			return nil, fmt.Errorf("验签失败:%s", verifyRsaSign.Error())
		}
	}
	if respMsg.ResCode != "0000" {
		fmt.Printf("响应信息: %s \n", string(resp))
		return nil, fmt.Errorf("%s", respMsg.ResMsg)
	}
	fmt.Printf("验签成功 \n")
	// （3）商户将resData密文用Base64解编码。
	resData, err := base64Decode(respMsg.ResData)
	if err != nil {
		return nil, errors.New("parseCommResp base64Decode failed. error: " + err.Error())
	}
	// （4）商户对Base64解编码后的字节数组用优付DES密钥解密得到明文字节数组。
	decryptResData, err := DESECBDecrypt(resData, []byte(cli.desKey))
	if err != nil {
		return nil, errors.New("decryptResData failed. error: " + err.Error())
	}
	fmt.Printf("【解密后的明文】 : %s\n", string(decryptResData))
	respMsg.ResData = decryptResData
	return respMsg, nil
}
